home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-01 / ohlutil.zip / RM.C < prev    next >
C/C++ Source or Header  |  1990-06-22  |  14KB  |  567 lines

  1. /* `rm' file deletion utility for GNU.
  2.    Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 1, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Written by Paul Rubin, David MacKenzie, and Richard Stallman. */
  19.  
  20. #include <stdio.h>
  21. #include <sys/types.h>
  22. #include <errno.h>
  23. #include "system.h"
  24. #include "getopt.h"
  25.  
  26. #ifdef STDC_HEADERS
  27. #include <stdlib.h>
  28. #include <errno.h>
  29. #else
  30. char *malloc ();
  31. char *realloc ();
  32.  
  33. extern int errno;
  34. #endif
  35.  
  36. char *basename ();
  37. char *stpcpy ();
  38. char *xmalloc ();
  39. char *xrealloc ();
  40. int check_stack ();
  41. int clear_directory ();
  42. int remove_dir ();
  43. int remove_file ();
  44. int rm ();
  45. int yesno ();
  46. void error ();
  47. void strip_trailing_slashes ();
  48. void usage ();
  49.  
  50. /* Path of file now being processed; extended as necessary. */
  51. char *pathname;
  52.  
  53. /* Amount of space currently allocated in `pathname';
  54.    made larger when necessary, but never smaller.  */
  55. int pnsize;
  56.  
  57. /* Name under which this program was run.  */
  58. char *program_name;
  59.  
  60. /* If nonzero, display the name of each file removed. */
  61. int verbose;
  62.  
  63. /* If nonzero, ignore unremovable files (print no error messages and
  64.    always exit with a status of zero). */
  65. int ignore_errors;
  66.  
  67. /* If nonzero, recursively remove directories. */
  68. int recursive;
  69.  
  70. /* If nonzero, ask no questions. */
  71. int override_mode;
  72.  
  73. /* If nonzero, query the user about whether to remove each file. */
  74. int interactive;
  75.  
  76. /* If nonzero, stdin is not connected to a tty. */
  77. int stdin_not_tty;
  78.  
  79. struct option long_opts[] =
  80. {
  81.   {"force", 0, NULL, 'f'},
  82.   {"interactive", 0, &interactive, 1},
  83.   {"override", 0, &override_mode, 1},
  84.   {"recursive", 0, &recursive, 1},
  85.   {"verbose", 0, &verbose, 1},
  86.   {NULL, 0, NULL, 0}
  87. };
  88.  
  89. void
  90. main (argc, argv)
  91.      int argc;
  92.      char **argv;
  93. {
  94.   int err = 0;
  95.   int c;
  96.   int ind;
  97.  
  98.   verbose = ignore_errors = recursive = interactive = override_mode = 0;
  99.   pnsize = 256;
  100.   pathname = xmalloc (pnsize);
  101.   program_name = argv[0];
  102.  
  103.   while ((c = getopt_long (argc, argv, "fiorvR", long_opts, &ind)) != EOF)
  104.     {
  105.       if (c == 0 && long_opts[ind].flag == 0)
  106.     c = long_opts[ind].val;
  107.       switch (c)
  108.     {
  109.     case 0:            /* Long option. */
  110.       break;
  111.     case 'f':
  112.       ignore_errors = 1;
  113.       override_mode = 1;
  114.       break;
  115.     case 'i':
  116.       interactive = 1;;
  117.       break;
  118.     case 'o':
  119.       override_mode = 1;
  120.       break;
  121.     case 'r':
  122.     case 'R':
  123.       recursive = 1;
  124.       break;
  125.     case 'v':
  126.       verbose = 1;
  127.       break;
  128.     default:
  129.       usage ();
  130.     }
  131.     }
  132.  
  133.   if (optind == argc)
  134.     usage ();
  135.  
  136.   if (interactive)
  137.     {
  138.       override_mode = 0;
  139.       ignore_errors = 0;
  140.     }
  141.   stdin_not_tty = !isatty (0);
  142.  
  143.   for (; optind < argc; optind++)
  144.     {
  145.       int len;
  146.  
  147.       strip_trailing_slashes (argv[optind]);
  148.       if (!strcmp (basename (argv[optind]), ".."))
  149.     {
  150.       if (!ignore_errors)
  151.         error (0, 0, "removal of `%s' is not allowed", argv[optind]);
  152.       err++;
  153.       continue;
  154.     }
  155.  
  156.       len = strlen (argv[optind]);
  157.       if (len + 1 > pnsize)
  158.     {
  159.       free (pathname);
  160.       pnsize = 2 * (len + 1);
  161.       pathname = xmalloc (pnsize);
  162.     }
  163.       strcpy (pathname, argv[optind]);
  164.       err += rm ();
  165.     }
  166.  
  167.   exit (err > 0 && !ignore_errors);
  168. }
  169.  
  170. /* Remove file or directory `pathname' after checking appropriate things.
  171.    Return 0 if `pathname' is removed, 1 if not. */
  172.  
  173. int
  174. rm ()
  175. {
  176.   struct stat sbuf;
  177.  
  178.   if (lstat (pathname, &sbuf) < 0)
  179.     {
  180.       if (!ignore_errors)
  181.     error (0, errno, "%s", pathname);
  182.       return 1;
  183.     }
  184.  
  185.   if (verbose)
  186.     printf ("  %s\n", pathname);
  187.  
  188.   if ((sbuf.st_mode & S_IFMT) == S_IFDIR)
  189.     return remove_dir (&sbuf);
  190.   else
  191.     return remove_file (&sbuf);
  192. }
  193.  
  194. /* Query the user if appropriate, and if ok try to remove the
  195.    non-directory `pathname', which `statp' contains info about.
  196.    Return 0 if `pathname' is removed, 1 if not. */
  197.  
  198. int
  199. remove_file (statp)
  200.      struct stat *statp;
  201. {
  202.   if (interactive)
  203.     {
  204.       fprintf (stderr, "%s: remove `%s'? ", program_name, pathname);
  205.       if (!yesno ())
  206.     return 1;
  207.     }
  208.   else if (!override_mode)
  209.     {
  210.       int may_overwrite;
  211.  
  212.       /* Treat the file as nonwritable if it lacks write permission bits,
  213.      even if we are root.  */
  214. #ifdef S_IFLNK
  215.       if ((statp->st_mode & S_IFMT) == S_IFLNK)
  216.     may_overwrite = 1;
  217.       else
  218. #endif
  219.     may_overwrite = eaccess_stat (statp, W_OK) == 0
  220.       && (statp->st_mode & 0222);
  221.       
  222.       if (!may_overwrite)
  223.     {
  224.       if (stdin_not_tty)
  225.         {
  226.           if (!ignore_errors)
  227.         error (0, 0, "%s: no write permission", pathname);
  228.           return 1;
  229.         }
  230.       fprintf (stderr, "%s: override mode %04o for `%s'? ",
  231.            program_name, statp->st_mode & 0777, pathname);
  232.       if (!yesno ())
  233.         return 1;
  234.     }
  235.     }
  236.  
  237.   if (unlink (pathname))
  238.     {
  239.       if (!ignore_errors)
  240.     error (0, errno, "%s", pathname);
  241.       return 1;
  242.     }
  243.   return 0;
  244. }
  245.  
  246. /* If not in recursive mode, print an error message and return 1.
  247.    Otherwise, query the user if appropriate, then try to recursively
  248.    remove directory `pathname', which `statp' contains info about.
  249.    Return 0 if `pathname' is removed, 1 if not. */
  250.  
  251. int
  252. remove_dir (statp)
  253.      struct stat *statp;
  254. {
  255.   int err;
  256.  
  257.   if (!recursive)
  258.     {
  259.       if (!ignore_errors)
  260.     error (0, 0, "`%s' is a directory", pathname);
  261.       return 1;
  262.     }
  263.  
  264.   if (eaccess_stat (statp, R_OK | X_OK))
  265.     {
  266.       /* POSIX.2 draft 9 says to only complain here if the dir is
  267.      not empty, but I can see no way to find out whether it is
  268.      empty without being able to read it. */
  269.       if (!ignore_errors)
  270.     error (0, errno, "%s", pathname);
  271.       return 1;
  272.     }
  273.  
  274.   if (interactive)
  275.     {
  276.       fprintf (stderr, "%s: recursively descend directory `%s'? ",
  277.            program_name, pathname);
  278.       if (!yesno ())
  279.     return 1;
  280.     }
  281.  
  282.   err = clear_directory (statp);
  283.   if (err == 0)
  284.     {
  285.       if (interactive)
  286.     {
  287.       fprintf (stderr, "%s: remove directory `%s'? ",
  288.            program_name, pathname);
  289.       if (!yesno ())
  290.         return 1;
  291.     }
  292.       err = rmdir (pathname) != 0;
  293.       if (err != 0 && !ignore_errors)
  294.     error (0, errno, "%s", pathname);
  295.     }
  296.   return err;
  297. }
  298.  
  299. /* An element in a stack of pointers into `pathname'.
  300.    `pathp' points to where in `pathname' the terminating '\0' goes
  301.    for this level's directory name. */
  302. struct pathstack
  303. {
  304.   struct pathstack *next;
  305.   char *pathp;
  306.   ino_t inum;
  307. };
  308.  
  309. /* Linked list of pathnames of directories in progress in recursive rm.
  310.    The entries actually contain pointers into `pathname'.
  311.    `pathstack' is the current deepest level. */
  312. static struct pathstack *pathstack = NULL;
  313.  
  314. /* Read directory `pathname' and remove all of its entries,
  315.    avoiding use of chdir.
  316.    On entry, `statp' points to the results of stat on `pathname'.
  317.    Return 0 for success, error count for failure.
  318.    Upon return, `pathname' will have the same contents as before,
  319.    but its address might be different; in that case, `pnsize' will
  320.    be larger, as well. */
  321.  
  322. int
  323. clear_directory (statp)
  324.      struct stat *statp;
  325. {
  326.   DIR *dirp;
  327.   struct direct *dp;
  328.   char *name_space;        /* Copy of directory's filenames. */
  329.   char *namep;            /* Current entry in `name_space'. */
  330.   unsigned name_size;        /* Bytes allocated for `name_space'. */
  331.   ino_t *inode_space;        /* Copy of directory's inodes. */
  332.   ino_t *inodep;        /* Current entry in `inode_space'. */
  333.   unsigned inode_size;        /* Bytes allocated for `inode_space'. */
  334.   int name_length;        /* Length of filename in `namep' plus '\0'. */
  335.   int pathname_length;        /* Length of `pathname'. */
  336.   int err = 0;            /* Return status. */
  337.   struct pathstack pathframe;    /* New top of stack. */
  338.   struct pathstack *pp;        /* Temporary. */
  339.  
  340.   errno = 0;
  341.   dirp = opendir (pathname);
  342.   if (dirp == NULL)
  343.     {
  344.       if (!ignore_errors)
  345.     error (0, errno, "%s", pathname);
  346.       return 1;
  347.     }
  348.  
  349.   name_size = statp->st_size;
  350.   name_space = (char *) xmalloc (name_size);
  351.   namep = name_space;
  352.  
  353.   inode_size = statp->st_size;
  354.   inode_space = (ino_t *) xmalloc (inode_size);
  355.   inodep = inode_space;
  356.  
  357.   while ((dp = readdir (dirp)) != NULL)
  358.     {
  359.       /* Skip "." and ".." (some NFS filesystems' directories lack them). */
  360.       if (dp->d_name[0] != '.'
  361.       || (dp->d_name[1] != '\0'
  362.           && (dp->d_name[1] != '.' || dp->d_name[2] != '\0')))
  363.     {
  364.       unsigned size_needed = (namep - name_space) + NLENGTH (dp) + 2;
  365.  
  366.       if (size_needed > name_size)
  367.         {
  368.           char *new_name_space;
  369.  
  370.           while (size_needed > name_size)
  371.         name_size += 1024;
  372.  
  373.           new_name_space = xrealloc (name_space, name_size);
  374.           namep += new_name_space - name_space;
  375.           name_space = new_name_space;
  376.         }
  377.       namep = stpcpy (namep, dp->d_name) + 1;
  378.  
  379.       if (inodep == inode_space + inode_size)
  380.         {
  381.           ino_t *new_inode_space;
  382.  
  383.           inode_size += 1024;
  384.           new_inode_space = (ino_t *) xrealloc (inode_space, inode_size);
  385.           inodep += new_inode_space - inode_space;
  386.           inode_space = new_inode_space;
  387.         }
  388.       *inodep++ = dp->d_ino;
  389.     }
  390.     }
  391.   *namep = '\0';
  392.   closedir (dirp);
  393.   
  394.   pathname_length = strlen (pathname);
  395.  
  396.   for (namep = name_space, inodep = inode_space; *namep != '\0';
  397.        namep += name_length, inodep++)
  398.     {
  399.       name_length = strlen (namep) + 1;
  400.  
  401.       /* Satisfy GNU requirement that filenames can be arbitrarily long. */
  402.       if (pathname_length + 1 + name_length > pnsize)
  403.     {
  404.       char *new_pathname;
  405.  
  406.       pnsize = (pathname_length + 1 + name_length) * 2;
  407.       new_pathname = xrealloc (pathname, pnsize);
  408.       /* Update the all the pointers in the stack to use the new area. */
  409.       for (pp = pathstack; pp != NULL; pp = pp->next)
  410.         pp->pathp += new_pathname - pathname;
  411.       pathname = new_pathname;
  412.     }
  413.  
  414.       /* Add a new frame to the top of the path stack. */
  415.       pathframe.pathp = pathname + pathname_length;
  416.       pathframe.inum = *inodep;
  417.       pathframe.next = pathstack;
  418.       pathstack = &pathframe;
  419.  
  420.       /* Append '/' and the filename to current pathname, take care of the
  421.      file (which could result in recursive calls), and take the filename
  422.      back off. */
  423.  
  424.       *pathstack->pathp = '/';
  425.       strcpy (pathstack->pathp + 1, namep);
  426.  
  427.       /* If the i-number has already appeared, there's an error. */
  428.       if (check_stack (pathstack->next, pathstack->inum) || rm ())
  429.     err++;
  430.  
  431.       *pathstack->pathp = '\0';
  432.       pathstack = pathstack->next;    /* Pop the stack. */
  433.     }
  434.   free (name_space);
  435.   free (inode_space);
  436.   return err;
  437. }
  438.  
  439. /* If STACK does not already have an entry with the same i-number as INUM,
  440.    return 0. Otherwise, ask the user whether to continue;
  441.    if yes, return 1, and if no, exit.
  442.    This assumes that no one tries to remove filesystem mount points;
  443.    doing so could cause duplication of i-numbers that would not indicate
  444.    a corrupted file system. */
  445.  
  446. int
  447. check_stack (stack, inum)
  448.      struct pathstack *stack;
  449.      ino_t inum;
  450. {
  451.   struct pathstack *p;
  452.  
  453.   for (p = stack; p != NULL; p = p->next)
  454.     {
  455.       if (p->inum == inum)
  456.     {
  457.       fprintf (stderr, "\
  458. %s: WARNING: Circular directory structure.\n\
  459. This almost certainly means that you have a corrupted file system.\n\
  460. NOTIFY YOUR SYSTEM MANAGER.\n\
  461. Cycle detected:\n\
  462. %s\n\
  463. is the same file as\n", program_name, pathname);
  464.       *p->pathp = '\0';    /* Truncate pathname. */
  465.       fprintf (stderr, "%s\n", pathname);
  466.       *p->pathp = '/';    /* Put it back. */
  467.       if (stdin_not_tty)
  468.         exit (1);
  469.       fprintf (stderr, "%s: continue? ", program_name);
  470.       if (!yesno ())
  471.         exit (1);
  472.       return 1;
  473.     }
  474.     }
  475.   return 0;
  476. }
  477.  
  478. /* Query the user for a line from the keyboard;
  479.    return 1 if yes, 0 otherwise. */
  480.  
  481. int
  482. yesno ()
  483. {
  484.   int c, c2;
  485.  
  486.   fflush (stderr);
  487.   c = getchar ();
  488.   if (c == '\n')
  489.     return 0;
  490.   while ((c2 = getchar ()) != '\n' && c2 != EOF)
  491.     ;
  492.  
  493.   return c == 'y' || c == 'Y';
  494. }
  495.  
  496. /* Remove trailing slashes from PATH; they cause some system calls to fail. */
  497.  
  498. void
  499. strip_trailing_slashes (path)
  500.      char *path;
  501. {
  502.   int last;
  503.  
  504.   last = strlen (path) - 1;
  505.   while (last > 0 && path[last] == '/')
  506.     path[last--] = '\0';
  507. }
  508.  
  509. char *
  510. xmalloc (n)
  511.      unsigned n;
  512. {
  513.   char *p;
  514.  
  515.   p = malloc (n);
  516.   if (p == 0)
  517.     error (2, 0, "virtual memory exhausted");
  518.   return p;
  519. }
  520.  
  521. char *
  522. xrealloc (p, n)
  523.      char *p;
  524.      unsigned n;
  525. {
  526.   p = realloc (p, n);
  527.   if (p == 0)
  528.     error (2, 0, "virtual memory exhausted");
  529.   return p;
  530. }
  531.  
  532. /* Return `name' with any leading path stripped off.  */
  533.  
  534. char *
  535. basename (name)
  536.      char *name;
  537. {
  538.   char *base;
  539.  
  540.   base = rindex (name, '/');
  541.   return base ? base + 1 : name;
  542. }
  543.  
  544. /* Copy SOURCE into DEST, stopping after copying the first '\0', and
  545.    return a pointer to the '\0' at the end of DEST;
  546.    in other words, return DEST + strlen (SOURCE). */
  547.  
  548. char *
  549. stpcpy (dest, source)
  550.      char *dest;
  551.      char *source;
  552. {
  553.   while ((*dest++ = *source++) != 0)
  554.     /* Do nothing. */ ;
  555.   return dest - 1;
  556. }
  557.  
  558. void
  559. usage ()
  560. {
  561.   fprintf (stderr, "\
  562. Usage: %s [-fiorvR] [+force] [+interactive] [+override] [+recursive]\n\
  563.        [+verbose] path...\n",
  564.        program_name);
  565.   exit (1);
  566. }
  567.